---
description:
  Automatic Persisted Queries is a protocol for reducing the overhead of sending the same GraphQL
  documents to the server over and over again.
---

import { Callout } from '@theguild/components'

# Automatic Persisted Queries

Automatic Persisted Queries is a protocol for reducing the overhead of sending the same GraphQL
documents to the server over and over again. Thus reducing client to server upstream traffic.

Since the upload speed can be the bottleneck from client to server, reducing the payload size can
improve the performance especially for huge GraphQL documents.

The GraphQL Yoga's Automatic Persisted Queries plugin follows
[the APQ Specification of Apollo](https://github.com/apollographql/apollo-link-persisted-queries#apollo-engine).

<Callout>
  Automatic Persisted Queries do not provide any security features, the benefit
  of using them is to reduce network overhead. If you want to avoid executing
  arbitrary GraphQL operations please use [Persisted
  Operations](/docs/features/persisted-operations).

Furthermore, an potential DDOS attacker could spam your GraphQL API with persisted operation
registrations, thus completly disable the advantages you would get from APQ and, furthermore, even
decrease the performance of your GraphQL API.

</Callout>

## Installation

## Quick Start

Using Automatic Persisted Queries requires installing a separate package.

```sh npm2yarn
npm i @graphql-yoga/plugin-apq
```

```ts filename="Automatic Persisted Queries Yoga setup" {3, 13}
import { createServer } from 'node:http'
import { createSchema, createYoga } from 'graphql-yoga'
import { useAPQ } from '@graphql-yoga/plugin-apq'

const yoga = createYoga({
  schema: createSchema({
    typeDefs: /* GraphQL */ `
      type Query {
        hello: String!
      }
    `
  }),
  plugins: [useAPQ()]
})

const server = createServer(yoga)
server.listen(4000, () => {
  console.info('Server is running on http://localhost:4000/graphql')
})
```

Start your yoga server and send a request for priming the cache (register the operation).

```bash filename="Execute GraphQL Operation to prime the cache"
curl -X POST -H 'Content-Type: application/json' http://localhost:4000/graphql \
  -d '{"query":"{__typename}","extensions":{"persistedQuery":{"version":1,"sha256Hash":"ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38"}}}'
```

Then afterwards we can send the same payload again, but this time omit the `query` field.

```bash filename="Execute GraphQL Operation without query payload"
curl -X POST -H 'Content-Type: application/json' http://localhost:4000/graphql \
  -d '{"extensions":{"persistedQuery":{"version":1,"sha256Hash":"ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38"}}}'
```

Especially for big GraphQL document strings, the subsequent payload can be much smaller.

## Client Usage

GraphQL clients such `Apollo Client` and `Urql` support Automatic Persisted Queries out of the box.
Check the corresponding documentation for more information.

- [Apollo Client](https://www.apollographql.com/docs/apollo-server/performance/apq/#step-2-enable-automatic-persisted-queries)
- [Urql](https://formidable.com/open-source/urql/docs/advanced/persistence-and-uploads/)

## Custom Store

By default all the documents strings are stored in memory with an LRU cache that holds up to 1000
unique entries.

A custom store implementation can be provided via the `store` option.

```ts filename="Automatic Persisted Operations with a custom store" {16}
import { createServer } from 'node:http'
import { createSchema, createYoga } from 'graphql-yoga'
import { useAPQ } from '@graphql-yoga/plugin-apq'

// Note: this store grows infinitely, so it is not a good idea to use it in production.
const store: APQStore = new Map()

const yoga = createYoga({
  schema: createSchema({
    typeDefs: /* GraphQL */ `
      type Query {
        hello: String!
      }
    `
  }),
  plugins: [useAPQ({ store })]
})

const server = createServer(yoga)
server.listen(4000, () => {
  console.info('Server is running on http://localhost:4000/graphql')
})
```

For external stores the `set` and `get` properties on the store can also return a `Promise`.

<Callout>
  In production, it's recommended to capture the errors from any store that could stop functioning.
  Instead of raising an error, returning undefined or null will allow the server to continue to
  respond to requests if the store goes down.

```ts filename="Automatic Persisted Operations with a redis store" {16}
import Keyv from 'keyv'

const store = new Keyv('redis://user:pass@localhost:6379')

useAPQ({
  store: {
    async get(key) {
      try {
        return await store.get(key)
      } catch (e) {
        console.error(`Error while fetching the operation: ${key}`, e)
      }
    },
    async set(key, value) {
      try {
        return await store.set(key, value)
      } catch (e) {
        console.error(`Error while saving the operation: ${key}`, e)
      }
    }
  }
})
```

</Callout>

## Configure Error responses

By default, responses for missing or mismatching query will include `extensions` property with HTTP
status code.

For example:

```ts {4}
{
  extensions: {
    http: {
      status: 404
    },
    code: 'PERSISTED_QUERY_NOT_FOUND'
  }
}
```

You can force the error responses to use 200 OK status code:

```ts filename="Automatic Persisted Operations with a custom store" {18-20}
import { createServer } from 'node:http'
import { createSchema, createYoga } from 'graphql-yoga'
import { useAPQ } from '@graphql-yoga/plugin-apq'

// Note: this store grows infinitely, so it is not a good idea to use it in production.
const store: APQStore = new Map()

const yoga = createYoga({
  schema: createSchema({
    typeDefs: /* GraphQL */ `
      type Query {
        hello: String!
      }
    `
  }),
  plugins: [
    useAPQ({
      responseConfig: {
        forceStatusCodeOk: true
      }
    })
  ]
})

const server = createServer(yoga)
server.listen(4000, () => {
  console.info('Server is running on http://localhost:4000/graphql')
})
```
